/**
 * @Author: lena
 * @Description:服务端
 * @Version: 3.0.0
 * @Date: 2021/9/22 14:27
 */

package version3

import (
	"fmt"
	"io"
	"net"
	"runtime"
	"strings"
	"sync"
)

type server struct {
	ip   string // 服务端ip
	port int    // 服务端端口号

	onlineMap map[string]*client // 存储在线用户列表
	mapLock   sync.RWMutex       // map是线程不安全的 当操作的时候需要用锁

	radioMessage chan string
}

// 创建一个服务端
func newServer(ip string, port int) *server {
	return &server{
		ip:           ip,
		port:         port,
		onlineMap:    make(map[string]*client), // 分配空间
		radioMessage: make(chan string),
	}
}

// 启动服务器的接口
func (this *server) start() {
	// 服务器监听端口
	listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", this.ip, this.port))
	if err != nil {
		fmt.Println("[server] listen err :", err)
		return
	}
	defer listen.Close()

	// 创建一个go负责监听存放要广播消息的通道
	go this.broadcastMessage()

	// 创建一个go负责监听服务端管道内的消息
	//go this.listenChannel()

	for {
		// 接收客户端连接请求，若没有则一直阻塞在这里
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("[server] accept err :", err)
			continue
		}
		// 处理接收到的数据
		// 另外开启一个协程来处理，是为了能够让当前协程能快速返回继续接收其他客户端的链接
		go this.handler(conn)
	}
}

// 处理客户端链接
func (this *server) handler(conn net.Conn) {
	// 为链接创建一个新的客户端
	client := newClient(conn, this.radioMessage)
	// 用户上线 将链接接入在线用户列表
	this.mapLock.Lock()
	this.onlineMap[client.name] = client
	this.mapLock.Unlock()
	// 广播当前用户上线消息
	this.inputBroadMessage(client, "已上线")
	// 为每一个客户端开启一个go 监听客户端是否有要发送的消息
	go this.listenClient(conn, client)
	// 阻塞当前handler 如果不阻塞，handler的协程会因为执行完而结束
	select {}
}

// 将要广播的消息存入通道中，client=发送消息的客户端，msg=要发送的消息
func (this *server) inputBroadMessage(client *client, msg string) {
	// 格式化要广播的信息，带上发送者
	sentMsg := "[" + client.name + "] " + msg
	// 将要广播的信息存入管道中，服务器会有协程专门负责读取该管道信息并广播
	this.radioMessage <- sentMsg
	fmt.Println("input :", sentMsg)
}

// 广播消息：当通道中有消息，就广播给在线用户
func (this *server) broadcastMessage() {
	for {
		// 读取通道中的信息
		msg := <-this.radioMessage
		// 获取在线用户，发送广播消息
		this.mapLock.RLock()
		for _, client := range this.onlineMap {
			// 将广播消息存入客户端的管道中，客户端会有协程负责读取管道中的信息
			client.channel <- msg
		}
		this.mapLock.RUnlock()
	}
}

// 监听客户端是否有要发送的消息
func (this *server) listenClient(conn net.Conn, client *client) {
	buf := make([]byte, 4096)
	for {
		// 读取客户端发送的消息，n=读取的字节长度
		n, err := conn.Read(buf)
		// 当客户端下线的时候，n=0
		if n == 0 {
			// 将下线消息存入广播通道
			this.inputBroadMessage(client, "已下线")
			// 从在线用户列表中删除当前用户
			this.mapLock.Lock()
			delete(this.onlineMap, client.name)
			this.mapLock.Unlock()
			// 用户已经下线，无需再监听
			return
		}
		if err != nil && err != io.EOF {
			fmt.Println("client read err :", err)
			return
		}
		// 第n个是"\n"符号，应该去除掉
		msg := string(buf[:n-1])
		// 私聊
		if len(msg) > 2 && msg[:2] == "to" {
			// 获取接收信息的客户端
			split := strings.Split(msg, " ")
			name := split[1]
			message := split[2]
			this.mapLock.RLock()
			// 接收者
			c := this.onlineMap[name]
			this.mapLock.RUnlock()
			if c == nil {
				// 接收者不存在 向发送方发送提示
				client.conn.Write([]byte("[error] 你要发送消息的用户不存在！请重新输入！" + "\n"))
				continue
			}
			msg := "[" + name + "]" + message
			// 向接收方发送消息
			c.conn.Write([]byte(msg + "\n"))
		} else if len(msg) > 6 && msg[:6] == "rename" {
			// 更新名字：rename
			split := strings.Split(msg, " ")
			newName := split[1]
			// 更新信息
			this.mapLock.Lock()
			// 1.更新map集合
			delete(this.onlineMap, client.name)
			this.onlineMap[newName] = client
			// 2.更新客户端信息
			client.name = newName
			this.mapLock.Unlock()
			// 提示客户更名成功
			client.conn.Write([]byte("更名成功！" + "\n"))
		} else if len(msg) >= 7 && msg[:7] == "offline" {
			// 用户下线
			this.inputBroadMessage(client, "已下线")
			// 从map中删除client
			this.mapLock.Lock()
			delete(this.onlineMap, client.name)
			this.mapLock.Unlock()
			// 释放资源
			defer close(client.channel) // 关闭管道
			defer client.conn.Close()   // 断开链接
			// 停止执行当前程序
			runtime.Goexit()
		} else if len(msg) >= 3 && strings.TrimSpace(msg) == "who" {
			client.conn.Write([]byte("在线用户列表如下：" + "\n"))
			// 查看当前在线用户：排除当前client
			i := 1
			for _, c := range this.onlineMap {
				// 排除当前client
				if c.name == client.name {
					continue
				}
				msg := fmt.Sprintf("[%d] %s", i, c.name)
				// 存入在线用户信息
				client.conn.Write([]byte(msg + "\n"))
				i++
			}
		} else if len(strings.TrimSpace(msg)) != 0 {
			// 广播消息
			this.inputBroadMessage(client, msg)
		}

	}
}
